﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace VideoDownloader
{
    /// <summary>
    /// 这个窗口服务内容：
    /// <para>--相当于youtube-dl.exe的调度程序，每次启动下载，总会启动一个进程，启动命令行去运行下载；</para>
    /// <para>----启动时：读取配置；注册热键；读取上次下载列表</para>
    /// <para>----退出时：保存配置；销毁热键；保存下载列表等</para>
    /// <para>使用法1：直接复制下载地址，然后按下对应快捷键，即可开启进程下载</para>
    /// <para>使用法2：下载地址拖拽到输入框，将直接启动下载</para>
    /// <para>使用法3：粘贴到输入框，点击启动下载</para>
    /// <para>注意：文件会下载到youtube-dl.exe所在地方</para>
    /// </summary>
    public partial class Form1 : Form
    {
        //常量区
        //--配置文件名：
        const string C_CfgFilePath = "cfg.txt";
        //--下载任务文件名
        const string C_DownloadingTaskFilePath = "downloadList.txt";

        //正在下载url
        private HashSet<string> hashSetUrlDownloading = new HashSet<string>();

        //已经完成下载的url，对应的控件：
        private Dictionary<string, Control> dictFinishUrl = new Dictionary<string, Control>();

        //youtube-dl.exe 所在路径
        private string m_youtubeDlExePath;

        // 定义委托事件（用于重定向输出youtube-dl.exe 的输入输出流）
        public event Action<RichTextBox, string> ReadStdOutput;
        public event Action<RichTextBox, string> ReadErrOutput;

        //--事件Id
        private enum KeyEvent { Call = 100 }

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 窗体加载时
        /// </summary>
        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                //加载配置内容
                if (!LoadConfig())
                {
                    Application.Exit();
                    return;
                }

                //读取上次未完的任务：
                LoadLastTask();

                //注册热键Shift+S，Id号为100。HotKey.KeyModifiers.Shift也可以直接使用数字4来表示。
                HotKey.RegisterHotKey(Handle, (int)KeyEvent.Call, HotKey.KeyModifiers.Alt | HotKey.KeyModifiers.Ctrl, Keys.D);

                //添加Debug输出方法（供Process回调使用）
                AddLogCallbackFunc();

                //添加销毁监听，用于保存事件等
                this.HandleDestroyed += Form1_HandleDestroyed;

                //添加拖拽事件监听（用于直接拉地址下载）
                this.AllowDrop = true;
                this.DragEnter += LinkAddressDragEnter;
                this.DragDrop += LinkAddressDragDrop;
            }
            catch (Exception ex)
            {
                MessageBox.Show("加载程序出错： " + ex.ToString(), ex.Message);
                Application.Exit();
            }
        }

        /// <summary>
        /// 读取下载配置（如果读取不成功，退出）
        /// </summary>
        private bool LoadConfig()
        {
            string[] cfgLines = null;
            if (File.Exists(C_CfgFilePath))
            {
                cfgLines = File.ReadAllLines(C_CfgFilePath);
            }

            if (cfgLines == null || cfgLines.Length < 1) //--无配置或无法读取到正确内容情况下，使用默认配置：
            {
                //默认配置内容：
                cfgLines = new string[1]
                {
                    Path.Combine(Application.StartupPath, "youtube-dl.exe"), //拼接默认路径+默认文件名
                };
            }

            //--配置内容0： 下载器的工作路径
            string dlExePathToFind = cfgLines[0];
            bool initSucc = TryInitDlExePath(dlExePathToFind);

            return initSucc;
        }

        private bool TryInitDlExePath(string dlExePathToFind)
        {
            bool initSucc = true;
            if (File.Exists(dlExePathToFind))
            {
                //采用此地址作为下载器路径：
                m_youtubeDlExePath = dlExePathToFind;
            }
            else
            {
                //文件路径无效，弹出选择：
                MessageBox.Show("没有找到对应的下载器文件（youtube-dl.exe），请选择exe所在路径");

                //弹窗，选择下载路径
                using (OpenFileDialog openFileDialog = new OpenFileDialog())
                {
                    openFileDialog.InitialDirectory = Application.StartupPath;
                    openFileDialog.Filter = "exe file (*.exe)|*.exe|All files (*.*)|*.*";
                    openFileDialog.FilterIndex = 1; //默认先选择exe
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.CheckFileExists = true;

                    bool selected = false;
                    while (!selected) //未有选择的情况下：一直弹出选择，或者退出程序：
                    {
                        if (openFileDialog.ShowDialog() == DialogResult.OK)
                        {
                            //Get the path of specified file
                            m_youtubeDlExePath = openFileDialog.FileName;
                            break;
                        }
                        else if ((MessageBox.Show("未有选择，退出？", "未有选择", MessageBoxButtons.YesNo) == DialogResult.Yes))
                        {
                            initSucc = false;
                            break;

                        }
                    }
                }

            }
            return initSucc;
        }

        /// <summary>
        /// 窗体销毁
        /// </summary>
        private void Form1_HandleDestroyed(object sender, EventArgs e)
        {
            //注销Id号为100的热键设定
            HotKey.UnregisterHotKey(Handle, (int)KeyEvent.Call);

            //--保存下载任务：
            SaveTasks();

            //--保存配置：
            File.WriteAllLines(C_CfgFilePath, new string[]
            {
                //----下载器目录：
                m_youtubeDlExePath,
            });
        }

        private void SaveTasks()
        {
            if (hashSetUrlDownloading.Count > 0)
            {
                string[] lines = new string[hashSetUrlDownloading.Count];
                hashSetUrlDownloading.CopyTo(lines);
                File.WriteAllLines(C_DownloadingTaskFilePath, lines);
            }
            else
            {
                //已经没有任务了，清空
                File.Delete(C_DownloadingTaskFilePath);
            }
        }

        /// <summary>
        /// 拖拽响应-开始
        /// </summary>
        private void LinkAddressDragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        /// <summary>
        /// 拖拽响应-拖拽完成 & 有效
        /// </summary>
        private void LinkAddressDragDrop(object sender, DragEventArgs e)
        {
            string linkText = (string)e.Data.GetData(DataFormats.Text);

            StartDownload(linkText);
        }

        /// <summary>
        /// 添加Debug输出方法（供Process回调使用）
        /// </summary>
        private void AddLogCallbackFunc()
        {
            ReadStdOutput += (rtb_console, str) =>
            {
                if (str == null)
                {
                    return;
                }
                rtb_console.AppendText(str); rtb_console.AppendText("\n");
            };

            ReadErrOutput += (rtb_console, str) =>
            {
                if (str == null)
                {
                    return;
                }
                rtb_console.AppendText("!!!!!!!!!!!!!ERROR!!!!!!!!!!!!!\n");
                rtb_console.AppendText(str); rtb_console.AppendText("\n");
            };
        }

        /// <summary>
        /// 读取上次未完的任务
        /// </summary>
        private void LoadLastTask()
        {
            if (!File.Exists(C_DownloadingTaskFilePath))
            {
                return;
            }

            var savedLines = File.ReadAllLines(C_DownloadingTaskFilePath);
            if (savedLines != null && savedLines.Length > 0)
            {
                var dialog = MessageBox.Show("上次任务未完，是否续下载？之前的任务还有：" + savedLines.Length, "个，是否继续下载？", MessageBoxButtons.YesNo);
                if (dialog == DialogResult.Yes)
                {
                    //续作上次任务：
                    foreach (var link in savedLines)
                    {
                        StartDownload(link);
                    }
                }
            }
        }

        private int downloadId = 0;
        /// <summary>
        /// 开始下载，如果因故无法下载返回false
        /// </summary>
        private bool StartDownload(string downLink)
        {
            if (string.IsNullOrEmpty(downLink) || !Regex.IsMatch(downLink, @"[\.\/a-zA-Z]+"))
            {
                MessageBox.Show("貌似是无效的视频链接：\"" + downLink + "\"");
                return false;
            }

            if (hashSetUrlDownloading.Contains(downLink))
            {
                MessageBox.Show("已经在下载中啦~！！\n" + downLink);
                return false;
            }

            //Thread thread = new Thread(new ParameterizedThreadStart((pxx)=> {

            Process p = new Process();

            //--提取参数：
            //string wrkPath = Path.GetDirectoryName(m_youtubeDlExePath); //--取下载exe的目录
            //string wrkExe = Path.GetFileName(m_youtubeDlExePath); //--取exe的名字

            //设置要启动的应用程序
            p.StartInfo.FileName = "cmd.exe";
            //是否使用操作系统shell启动
            p.StartInfo.UseShellExecute = false;
            // 接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardInput = true;
            //不重定向
            p.StartInfo.RedirectStandardOutput = true;
            // 错误不重定向
            p.StartInfo.RedirectStandardError = true;
            //显示程序窗口
            p.StartInfo.CreateNoWindow = true;

            //启动程序
            p.Start();

            //向cmd窗口发送输入信息
            //--注意格式：会填充youtube-dl.exe 的全路径到命令行中，否则cmd遇到路径有空格的命令，会无法读取
            string cmdParam = string.Format("\"{0}\" -f best \"{1}\" & exit", m_youtubeDlExePath, downLink);
            p.StandardInput.WriteLine(cmdParam);

            //rtb_console.AppendText(p.StandardOutput.ReadToEnd());
            //rtb_console.AppendText("\n");


            TabPage downloadPage = new TabPage();
            downloadPage.Name = (++downloadId).ToString();
            downloadPage.Text = (downloadId).ToString();
            this.tabControl1.Controls.Add(downloadPage);

            RichTextBox rtb = new RichTextBox();
            rtb.Dock = DockStyle.Fill;
            rtb.AllowDrop = true;
            rtb.DragEnter += LinkAddressDragEnter;  //让输入框也接受Drop内容
            rtb.DragDrop += LinkAddressDragDrop;

            downloadPage.Controls.Add(rtb);

            tabControl1.SelectedTab = downloadPage;
            rtb.AppendText("添加下载任务：" + downLink + "\n");

            //记录此时下载的内容：
            hashSetUrlDownloading.Add(downLink);
            SaveTasks();

            p.OutputDataReceived += new DataReceivedEventHandler((obj, arg) =>
            {
                this.Invoke(ReadStdOutput, rtb, arg.Data);

                //当输出信息包含下载完成时，则视为下载完成，退出下载记录：
                if (arg.Data != null)
                {
                    if (Regex.IsMatch(arg.Data, "100% .* in .*")
                    || Regex.IsMatch(arg.Data, "been.*downloaded"))
                    {
                        this.Invoke(ReadStdOutput, rtb, "下载完成，清理此下载任务");
                        hashSetUrlDownloading.Remove(downLink);

                        //记录对应的Tab 控件，用于稍后删除
                        dictFinishUrl.Add(downLink, downloadPage);

                        SaveTasks();
                    }
                }
            });

            p.ErrorDataReceived += new DataReceivedEventHandler((obj, arg) =>
            {
                this.Invoke(ReadErrOutput, rtb, arg.Data);
            });


            p.BeginErrorReadLine();
            p.BeginOutputReadLine();
            return true;

        }


        /// <summary>
        ///  监视Windows消息
        ///  重载WndProc方法，用于实现热键响应
        /// </summary>
        protected override void WndProc(ref Message m)
        {

            const int WM_HOTKEY = 0x0312;
            //按快捷键 
            switch (m.Msg)
            {
                case WM_HOTKEY:
                    switch (m.WParam.ToInt32())
                    {
                        case (int)KeyEvent.Call:    //快捷键响应
                            string txt = Clipboard.GetText();
                            if (!StartDownload(txt))//如果无法开启下载，则显示界面
                            {
                                this.Activate();
                                this.Show();
                            }
                            break;
                    }
                    break;
            }
            base.WndProc(ref m);
        }

        /// <summary>
        /// 开始按钮
        /// </summary>
        private void btn_startDownload_Click(object sender, EventArgs e)
        {
            StartDownload(tb_srcUrl.Text);

            tb_srcUrl.Text = "";

            this.Activate();
        }

        /// <summary>
        /// 关闭Tab按钮
        /// </summary>
        private void ToolStripMenuItem_CloseCurrentTag_Click(object sender, EventArgs e)
        {
            if (tabControl1.Controls.Count > 0)
            {
                tabControl1.Controls.RemoveAt(tabControl1.SelectedIndex);
            }
        }

        /// <summary>
        /// 关闭所有Tab按钮
        /// </summary>
        private void ToolStripMenuItem_CloseAllTag_Click(object sender, EventArgs e)
        {
            while (tabControl1.Controls.Count > 0)
            {
                tabControl1.Controls.RemoveAt(tabControl1.SelectedIndex);
            }

        }

        /// <summary>
        /// 关闭所有完成任务的Tag
        /// </summary>
        private void ToolStripMenuItem_CloseAllFinishTag_Click(object sender, EventArgs e)
        {
            if (dictFinishUrl.Count <= 0)
            {
                return;
            }

            foreach (var tabToDelete in dictFinishUrl.Values)
            {
                if (tabToDelete == null || tabToDelete.IsDisposed) //判断下tab控件情况（可能被其他按钮移除过了）
                {
                    continue;
                }
                else if (tabControl1.Controls.Contains(tabToDelete))
                {
                    tabControl1.Controls.Remove(tabToDelete);
                }
            }

            dictFinishUrl.Clear();
        }

        /// <summary>
        /// 窗口是否最前的选框事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cb_alwaysOnTop_CheckedChanged(object sender, EventArgs e)
        {
            this.TopMost = cb_alwaysOnTop.Checked;
        }

        private void ToolStripMenuItem_HotKeyStart_Click(object sender, EventArgs e)
        {

        }

    }
}
